/*
 * Copyright (C), 2002-2015, 你的兔宝宝电子商务有限公司
 * FileName: XmlUtils.java
 * Author:   你的兔宝宝
 * Date:     2015-3-22 下午5:40:39
 * Description: xml工具类//模块目的、功能描述      
 * History: //修改记录
 * <author>      <time>      <version>    <desc>
 * 修改人姓名             修改时间            版本号                  描述
 */
package com.baby.demo.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.dom4j.Attribute;
import org.dom4j.Branch;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 〈xml工具类〉<br>
 * 〈xml工具类,xml转map〉
 * 
 * @author 你的兔宝宝
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本] （可选）
 */
public class Xmls {

    private static final Logger LOGGER = LoggerFactory.getLogger(Xmls.class);

    public static final int XML_HEAD_LENGTH = 39;

    public static final String XML_HEAD = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";

    public static final String HELPER_BODY = "helper_body";

    public static final String HELPER_BODY_START = "<helper_body>";

    public static final String HELPER_BODY_END = "</helper_body>";
    
    private Xmls() {
    }
    
    /**
     * 写入xml 〈一句话功能简述〉 〈功能详细描述〉
     * 
     * @14040873
     * @param document
     * @param outPutXml
     * @return
     * @see [相关类/方法]（可选）
     * @since [产品/模块版本] （可选）
     */
    public static boolean writeXml(Document document, File outPutXml) {
        boolean flag = true;
        XMLWriter output = null;
        try {
            // 美化格式
            OutputFormat format = OutputFormat.createPrettyPrint();
            // 指定XML编码
            format.setEncoding("UTF-8");

            if (!outPutXml.exists()) {
                FileUtils.touch(outPutXml);
            }
            output = new XMLWriter(new OutputStreamWriter(new FileOutputStream(outPutXml), "UTF-8"), format);
            output.write(document);
        } catch (IOException e) {
            flag = false;
            LOGGER.error("write xml-io error", e);
        } finally {
            if (null != output) {
                try {
                    output.close();
                } catch (IOException e) {
                    flag = false;
                    LOGGER.error("write xml-io error", e);
                }
            }
        }
        return flag;
    }

    /**
     * 写入xml 〈一句话功能简述〉 〈功能详细描述〉
     * 
     * @14040873
     * @param document
     * @param path
     * @return
     * @see [相关类/方法]（可选）
     * @since [产品/模块版本] （可选）
     */
    public static boolean writeXml(Document document, String path) {
        File outPutXml = new File(path);
        return writeXml(document, outPutXml);
    }

    @SuppressWarnings("unchecked")
    public static List<Element> getNodesOfXml(String path, String node) {
        SAXReader sr = new SAXReader();
        Document doc = null;
        List<Element> list = null;
        try {
            doc = sr.read(path);
            if (null != doc) {
                list = doc.selectNodes("//" + node);
            }
        } catch (DocumentException e) {
            LOGGER.error("get node of xml error", e);
        }
        return list;
    }

    public static boolean writeXml(String xml, String path) throws DocumentException {
        Document document = DocumentHelper.parseText(xml);
        return writeXml(document, path);
    }

    /**
     * 
     * 功能描述: 删除含有指定属性值的节点<br>
     * 
     * @param path xml文件路径
     * @param node xml 节点名称
     * @param attribute xml 节点属性名称
     * @param attrValue xml节点属性值 例： <modules name="F"> <module prototype="sn-sys-123"/><module prototype="sn-sys-678"/>
     *            </modules> 参数node=module;attribute=prototype;attrValue=sn-sys-678； 返回<modules name="F"><module
     *            prototype="sn-sys-123"/></modules>
     */
    @SuppressWarnings("unchecked")
    public static boolean delNode(String path, String node, String attribute, String attrValue) {
        SAXReader sr = new SAXReader();
        Document doc = null;
        List<Element> elementList = null;
        boolean flag = true;
        boolean reWrite = false;
        try {
            doc = sr.read(path);
            if (null != doc) {
                elementList = doc.selectNodes("//" + node);
            }
            if (elementList == null) {
                return flag;
            }
            for (Element e : elementList) {
                String value = e.attributeValue(attribute);
                if (attrValue.equals(value)) {
                    Element parentE = e.getParent();
                    parentE.remove(e);
                    reWrite = true;
                }
            }

            if (reWrite) {
                flag = writeXml(doc, path);
            }
        } catch (DocumentException e) {
            flag = false;
            LOGGER.error("del node error", e);
        }
        return flag;
    }

    /**
     * 
     * 功能描述: 把xml文档转换为map<br>
     * 〈功能详细描述〉
     * 
     * @param doc
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public static Map<String, Object> xml2map(Document doc) {
        return xml2map(doc, false);
    }

    public static Map<String, Object> xml2map(Document doc, boolean rootneed) {
        Map<String, Object> map = new HashMap<String, Object>();
        if (doc == null) {
            return map;
        }
        Element root = doc.getRootElement();
        map = xml2map(root);
        // 根节点是否需要
        if (rootneed) {
            Map<String, Object> rootmap = new HashMap<String, Object>();
            String text = Strings.show(root.getTextTrim());
            if (!Collections.isEmpty(map)) {
                if (!Strings.isEmpty(text)) {
                    map.put("text", text);
                }
                rootmap.put(root.getName(), map);
            } else {
                rootmap.put(root.getName(), text);
            }
            return rootmap;
        }
        return map;
    }
    
    public static Map<String, Object> xml2map(InputStream is, boolean rootneed) {
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(is);
        } catch (Exception e) {
            LOGGER.error("读取文件失败", e);
        } finally {
            Files.closeQuietly(is);
        }
        return xml2map(document, rootneed);
    }
    

    /**
     * 
     * 功能描述: 把xml字符串转化为map<br>
     * 〈功能详细描述〉
     * 
     * @param xml
     * @return
     * @throws DocumentException
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public static Map<String, Object> xml2map(String xml) throws DocumentException {
        return xml2map(xml, false);
    }

    public static Map<String, Object> xml2map(String xml, boolean needroot) throws DocumentException {
        Document doc = DocumentHelper.parseText(xml);
        return xml2map(doc, needroot);
    }

    @SuppressWarnings("unchecked")
    public static Map<String, Object> xml2map2(String xml, boolean needroot) throws DocumentException {
        String nxml = xml;
        if (needroot) {
            nxml = HELPER_BODY_START + xml + HELPER_BODY_END;
        }
        Document doc = DocumentHelper.parseText(nxml);
        Map<String, Object> map = xml2map(doc, needroot);
        if (needroot) {
            map = (Map<String, Object>) map.get(HELPER_BODY);
        }
        return map;
    }

    /**
     * 
     * 功能描述: 把元素文档转换为map<br>
     * 〈功能详细描述〉
     * 
     * @param e
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> xml2map(Element e) {
        Map<String, Object> map = new HashMap<String, Object>();
        List<Element> list = e.elements();
        //有子元素
        if (!Collections.isEmpty(list)) {
            //循环处理在元素
            for (int i = 0; i < list.size(); i++) {
                
                Element elm = list.get(i);
                String attrname = elm.getName();
                Object attrvalue;
                Object attr = map.get( attrname );
                //是否有子元素处理
                List<Element> subelms = elm.elements();
                if( Collections.isEmpty(subelms) ){
                    attrvalue = attr(elm);
                }else{
                    attrvalue = xml2map(elm);
                }
                //是否为list情况处理
                if ( attr != null ) {
                    List<Object> mapList = makeList(attr);
                    mapList.add(attrvalue);
                    attr = mapList;
                } else {
                    attr = attrvalue;
                }
                map.put( attrname , attr );
                
            }
        }
        
        // 属性处理
        attr2map(e,map);
        
        return map;
    }

    @SuppressWarnings("unchecked")
    public static List<Object> makeList(Object attr){
        List<Object> mapList;
        String classname = attr.getClass().getName();
        if ( !"java.util.ArrayList".equals( classname ) ) {
            mapList = new ArrayList<Object>();
            mapList.add(attr);
        } else {
            mapList = (List<Object>) attr;
        }
        return mapList;
    }
    
    /**
     * 
     * 功能描述: 处理本元素的属性附加到本元素上<br>
     * 〈功能详细描述〉
     *
     * @param elm
     * @param map
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    @SuppressWarnings("unchecked")
    public static void attr2map(Element elm,Map<String,Object> map){
        String textName = "text";
        String text = Strings.show(elm.getTextTrim());
        if( Collections.isEmpty(map) ){
            textName = elm.getName();
        }
        if( Strings.isNotBlank(text) ){
            map.put(textName, text );
        }
        //处理属性
        List<Attribute> attributes = elm.attributes();
        Collections.copy(attr2map(attributes), map);
    }

    public static Map<String, Object> attr2map(List<Attribute> attributes) {
        Map<String, Object> attrs = new HashMap<String, Object>();
        for (int i = 0; i < attributes.size(); i++) {
            Attribute attr = attributes.get(i);
            attrs.put(attr.getName(), attr.getValue());
        }
        return attrs;
    }
    
    /**
     * 
     * 功能描述: 获取元素属性为map<br>
     * 〈功能详细描述〉
     *
     * @param elm
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public static Map<String,Object> attr2map(Element elm){
        Map<String,Object> map = Maps.noop();
        String text = Strings.show(elm.getTextTrim());
        Object attr = attr(elm);
        String attrName = elm.getName();
        if( attr instanceof Map ){
            Collections.copy4obj( attr , map);
        }else{
            map.put(attrName, text );
        }
        return map;
    }
    
    /**
     * 
     * 功能描述: 获取元素属性<br>
     * 〈功能详细描述〉
     *
     * @param elm
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    @SuppressWarnings("unchecked")
    public static Object attr(Element elm){
        String text = Strings.show(elm.getTextTrim());
        Object attr = text;
        //处理属性
        Map<String,Object> attrs = attr2map(elm.attributes());
        if( !Collections.isEmpty(attrs) ){
            attrs.put("text", text);
            attr = attrs;
        }
        return attr;
    }

    /**
     * 
     * 功能描述: xml文件转map<br>
     * 〈功能详细描述〉
     * 
     * @param filePath
     * @return
     * @throws DocumentException
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public static Map<String, Object> xml2map4file(String filePath) throws DocumentException {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(filePath);
        return xml2map(document);
    }

    /**
     * 
     * 功能描述: map转xml<br>
     * 〈功能详细描述〉
     * 
     * @param msgHash
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public static String map2xml(Map<String, Object> msgHash) {
        try {
            ParseObject2Xml parseObject2Xml = new ParseObject2Xml();
            String msg = parseObject2Xml.getMsg2(msgHash);
            return msg.substring(XML_HEAD_LENGTH, msg.length());
        } catch (IllegalArgumentException e) {
            LOGGER.error("HashMap转换Xml异常:{}", new String[] { e.getMessage() }, e);
            throw e;
        }
    }

    /**
     * 
     * 功能描述: map转xml<br>
     * 〈功能详细描述〉
     * 
     * @param map
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    @SuppressWarnings("unchecked")
    public static String map2xml2(Map<String, Object> map) {
        Document doc = map2doc(map, HELPER_BODY);
        List<Element> elements = doc.getRootElement().elements();
        StringBuilder sb = new StringBuilder();
        int length = elements.size();
        for (int i = 0; i < length; i++) {
            Element elm = elements.get(i);
            sb.append(elm.asXML());
        }
        return sb.toString();
    }

    /**
     * 根据Map组装xml消息体，值对象仅支持基本数据类型、String、BigInteger、BigDecimal，以及包含元素为上述支持数据类型的Map
     * 
     * @param vo
     * @param rootElement
     * @return
     */
    public static String map2xmlBody(Map<String, Object> vo, String rootElement) {
        return map2doc(vo, rootElement).asXML();
    }

    /**
     * 
     * 功能描述: map转doc<br>
     * 〈功能详细描述〉
     * 
     * @param vo
     * @param rootElement
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public static Document map2doc(Map<String, Object> vo, String rootElement) {
        org.dom4j.Document doc = DocumentHelper.createDocument();
        if (Strings.isNotBlank(rootElement)) {
            Element body = DocumentHelper.createElement(rootElement);
            buildMap2xmlBody(body, vo);
            doc.add(body);
        } else {
            buildMap2xmlBody(doc, vo);
        }
        return doc;
    }

    @SuppressWarnings("unchecked")
    private static void buildMap2xmlBody(Branch body, Map<String, Object> vo) {
        vo = Collections.show(vo);
        for (Entry<String, Object> entry : vo.entrySet()) {
            String key = entry.getKey();
            Object obj = vo.get(key);
            if ( obj == null ) {
                continue;
            }
            if (obj instanceof java.util.List) {
                List<Object> olist = (List<Object>) obj;
                for (int i = 0; i < olist.size(); i++) {
                    Element element = DocumentHelper.createElement(key);
                    Map<String, Object> voo = (Map<String, Object>) olist.get(i);
                    buildMap2xmlBody(element, voo);
                    body.add(element);
                }
            } else if (obj instanceof java.util.Map) {
                Element element = DocumentHelper.createElement(key);
                buildMap2xmlBody(element, (Map<String, Object>) obj);
                body.add(element);
            } else {
                Element element = DocumentHelper.createElement(key);
                if (obj instanceof java.lang.String) {
                    element.setText((String) obj);
                } else if (obj instanceof java.lang.Character || obj instanceof java.lang.Boolean || obj instanceof java.lang.Number
                        || obj instanceof java.math.BigInteger || obj instanceof java.math.BigDecimal
                        ) {
                    element.setText(String.valueOf(obj));
                }
                body.add(element);
            }
        }
    }

    /**
     * 根据xml消息体转化为Map
     * 
     * @param xml
     * @param rootElement
     * @return
     * @throws DocumentException
     * @author 蔡政滦 modify by 2015-6-5
     */
    public static Map<String, Object> xmlBody2map(String xml, String rootElement) throws DocumentException {
        org.dom4j.Document doc = DocumentHelper.parseText(xml);
        Element body = doc.getRootElement();
        if (Strings.isNotBlank(rootElement)) {
            body = (Element) doc.selectSingleNode("/" + rootElement);
        }
        Map<String, Object> vo = buildXmlBody2map(body);
        Map<String, Object> bodyvo = new HashMap<String, Object>();
        bodyvo.put(body.getName(), vo);
        return bodyvo;
    }

    private static Object buildValue(String type,String text, Element element){
        Object value = null;
        if (java.lang.String.class.getCanonicalName().equals(type)) {
            value = text;
        } else if (java.lang.Character.class.getCanonicalName().equals(type)) {
            value = Character.valueOf(text.charAt(0));
        } else if (java.lang.Boolean.class.getCanonicalName().equals(type)) {
            value = Boolean.valueOf(text);
        } else if ( java.lang.Short.class.getCanonicalName().equals(type) 
                || java.lang.Integer.class.getCanonicalName().equals(type)
                || java.lang.Long.class.getCanonicalName().equals(type)
                || java.lang.Float.class.getCanonicalName().equals(type)
                || java.lang.Double.class.getCanonicalName().equals(type)
                || java.math.BigInteger.class.getCanonicalName().equals(type)
                || java.math.BigDecimal.class.getCanonicalName().equals(type)
                ) {
            value = Numbers.parse(type, text);
        } else if (java.util.Map.class.getCanonicalName().equals(type)) {
            value = buildXmlBody2map(element);
        }
        return value;
    }
    
    @SuppressWarnings("unchecked")
    private static Map<String, Object> buildXmlBody2map(Element body) {
        Map<String, Object> vo = new HashMap<String, Object>();
        if (body == null) {
            return vo;
        }
        List<Element> elements = body.elements();
        for (Element element : elements) {
            String key = element.getName();
            if (Strings.isNotBlank(key)) {
                String type = element.attributeValue("type", "java.lang.String");
                String text = element.getTextTrim();
                Object value;
                if (!Collections.isEmpty(element.elements())) {
                    value = buildXmlBody2map(element);
                } else {
                    value = buildValue(type,text,element);
                }
                Object ovalue = vo.get(key);
                if (ovalue != null) {
                    List<Object> olist = makeList(ovalue);
                    olist.add(value);
                    value = olist;
                }
                vo.put(key, value);
            }
        }
        return vo;
    }
    
    /**
     * 
     * 功能描述: <br>
     * 〈获取xml文档中指定的节点〉
     *
     * @param in 文件流
     * @param node 节点
     * @return
     */
    @SuppressWarnings("unchecked")
    public static List<Element> getNodesOfXml(InputStream in, String node) {
        SAXReader sr = new SAXReader();
        Document doc = null;
        List<Element> list = null;
        try {
            doc = sr.read(in);
            if (null != doc) {
                list = doc.selectNodes("//"+node);
            }
        } catch (DocumentException e) {
            LOGGER.error(e.getMessage(),e);
        }finally{
            IOUtils.closeQuietly(in);
        }
        return list;
    }
}
